﻿#region License

// Copyright (c) 2009, Bill O'Neill (william.w.oneill@gmail.com)
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without 
// modification, are permitted provided that the following conditions 
// are met:
// 
// 1. Redistributions of source code must retain the above copyright 
// notice, this list of conditions and the following disclaimer.
// 
// 2. Redistributions in binary form must reproduce the above copyright 
// notice, this list of conditions and the following disclaimer in the 
// documentation and/or other materials provided with the distribution.
// 
// 3. Neither the name of Focal Point Technology Solutions, LLC nor 
// the names of its contributors may be used to endorse or promote 
// products derived from this software without specific prior written 
// permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 
// CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 
// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 
// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 
// DAMAGE.

#endregion

using System;
using System.IO.Ports;
using X10d.Interface.States;
using System.Threading;

namespace X10d.Interface
{
	/// <summary>
	/// Manages the X10 interface and requests
	/// </summary>
	internal class RequestManager : IRequestManager
	{
		private readonly Semaphore mProcessingCommandSemaphore = new Semaphore(1, 1);

		/// <summary>
		/// Creates a new instance of the class
		/// </summary>
		/// <param name="serialPort">
		/// COM port where the X10 interface is attached
		/// </param>
		public RequestManager(int serialPort)
		{
			const int X10_SUPPORTED_DATA_BITS = 8;
			const int X10_SUPPORTED_BAUD = 4800;
			const string COM_PORT_PREFIX = "COM";

			CurrentState = new IdleState();

			Port = new SerialPort(
				COM_PORT_PREFIX + serialPort, X10_SUPPORTED_BAUD, Parity.None,
				X10_SUPPORTED_DATA_BITS, StopBits.One);

			Port.DataReceived += _HandleSerialPortDataReceived;
			Port.ErrorReceived += _HandleSerialPortErrorReceived;

			Port.Open();
		}

		#region IRequestManager Members

		public void TransitionToState( IPortState nextState )
		{
			Log.Info("Changing to state " + nextState.GetType());

			CurrentState = nextState;

			// Release the semaphore when returning to an idle state. 
			// Always set the current command to null when returing to an idle state.
			if (CurrentState is IdleState)
			{
				// Store away the reference before raising the event. The CurrentCommand should
				// be null before releasing the concurrency controls.
				CommandCompleteEventArgs args = new CommandCompleteEventArgs { Command = CurrentCommand };

				CurrentCommand = null;

				mProcessingCommandSemaphore.Release();

				// Fire the event last after releasing the concurrency controls. 
				// Don't let various event handlers block other threads.
				if (CommandComplete != null)
				{
					CommandComplete(this, args);
				}
			}

			CurrentState.HandleArrive(this);
			
		}

		/// <summary>
		/// Gets and sets the current state
		/// </summary>
		public IPortState CurrentState
		{ 
			get;
			private set;
		}

		/// <summary>
		/// Gets a reference to the serial port used to communicate with the X10 interface
		/// </summary>
		public SerialPort Port { get; private set; }

		/// <summary>
		/// Gets a reference to the current X10 command
		/// </summary>
		public DeviceCommand CurrentCommand { get; private set; }

		/// <summary>
		/// Raised when a command execution completes
		/// </summary>
		public event EventHandler<CommandCompleteEventArgs> CommandComplete;

		#endregion

		/// <summary>
		/// Requests the manager to execute a new X10 command
		/// </summary>
		/// <param name="newCommand">
		/// New X10 command to execute
		/// </param>
		public void ExecuteCommand(DeviceCommand newCommand)
		{
			if (newCommand == null)
			{
				throw new ArgumentNullException("newCommand");
			}

			// Wait until the FSA returns to an Idle state
			mProcessingCommandSemaphore.WaitOne();

			if (!(CurrentState is IdleState))
			{
				throw new InvalidOperationException(
					"The interface was not in the state to start processing a new command.");
			}

			CurrentCommand = newCommand;

			TransitionToState( new SendAddressState() );
		}

		private static void _HandleSerialPortErrorReceived(object sender, SerialErrorReceivedEventArgs e)
		{
			Log.Error("Error from COM port: " + e.EventType);
		}

		private void _HandleSerialPortDataReceived(object sender, SerialDataReceivedEventArgs e)
		{
			Log.Info("Data received from COM port");

			CurrentState.HandleNewData(this);
		}
	}
}